package com.wennn.top.security.service;

import com.wennn.top.common.vo.IResult;
import com.wennn.top.core.cache.RedisUtils;
import com.wennn.top.core.util.SWatch;
import com.wennn.top.security.config.JwtSetting;
import com.wennn.top.security.exception.TokenException;
import com.wennn.top.security.model.SessionContext;
import com.wennn.top.security.model.SessionUser;
import com.wennn.top.security.model.token.JwtAccessToken;
import com.wennn.top.security.model.token.JwtRawAccessToken;
import com.wennn.top.security.util.EncryptUtils;
import com.wennn.top.security.util.JwtUtil;
import com.wennn.top.security.util.SecurityConst;
import com.wennn.top.security.util.StringUtils;
import com.wennn.top.security.vo.AuthUser;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.web.authentication.www.NonceExpiredException;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author Zheng Jie
 * @Date 2019年10月26日21:56:27
 */
@Component
@Slf4j
public class SessionUserService {

    @Autowired
    @Getter
    private JwtSetting jwtSetting;

    @Autowired
    private RedisUtils redisUtils;

    /**
     * 保存在线用户信息
     *
     * @param authUser /
     */
    public void save(AuthUser authUser) {
        SessionUser sessionUser = authUser.getSessionUser();
        cacheSessionUser(sessionUser.getToken(), sessionUser);
    }

    /**
     * 查询全部数据
     *
     * @param filter   /
     * @param pageable /
     * @return /
     */
    public IResult getAll(String filter, int type, Pageable pageable) {
        List<SessionUser> onlineUsers = getAll(filter, type);
        return IResult.ok(toPage(pageable.getPageNumber(), pageable.getPageSize(), onlineUsers));
    }

    /**
     * 查询全部数据，不分页
     *
     * @param filter /
     * @return /
     */
    public List<SessionUser> getAll(String filter, int type) {
        List<String> keys = null;
        if (type == 1) {
            keys = redisUtils.scan("m-online-token*");
        } else {
            keys = redisUtils.scan(jwtSetting.getOnlineKey() + "*");
        }

        Collections.reverse(keys);

        List<SessionUser> onlineUsers = new ArrayList<>();
        for (String key : keys) {
            SessionUser onlineUser = (SessionUser) redisUtils.get(key);
            if (StringUtils.isNotBlank(filter)) {
                if (onlineUser.toString().contains(filter)) {
                    onlineUsers.add(onlineUser);
                }
            } else {
                onlineUsers.add(onlineUser);
            }
        }
        onlineUsers.sort((o1, o2) -> o2.getLoginTime().compareTo(o1.getLoginTime()));
        return onlineUsers;
    }

    /**
     * 踢出用户
     *
     * @param key /
     * @throws Exception /
     */
    public void kickOut(String key) throws Exception {
        key = jwtSetting.getOnlineKey() + EncryptUtils.desDecrypt(key);
        redisUtils.del(key);
    }

    /**
     * 踢出移动端用户
     *
     * @param key /
     * @throws Exception /
     */
    public void kickOutT(String key) throws Exception {
        String keyt = jwtSetting.getOnlineKey() + EncryptUtils.desDecrypt(key);
        redisUtils.del(keyt);
    }

    /**
     * 退出登录
     *
     * @param token /
     */
    public void logout(String token) {
        String key = jwtSetting.getOnlineKey() + token;
        redisUtils.del(key);
    }

    /**
     * 查询用户
     *
     * @param key /
     * @return /
     */
    public SessionUser getOne(String key) {
        return (SessionUser) redisUtils.get(key);
    }

    /**
     * 检测用户是否在之前已经登录，已经登录踢下线
     *
     * @param userName 用户名
     */
    public void checkLoginOnUser(String userName, String igoreToken) {
        List<SessionUser> onlineUsers = getAll(userName, 0);
        if (onlineUsers == null || onlineUsers.isEmpty()) {
            return;
        }

        onlineUsers.parallelStream().forEach(onlineUser -> {
            try {
                String token = EncryptUtils.desDecrypt(onlineUser.getKey());
                if (StringUtils.isNotBlank(igoreToken) && !igoreToken.equals(token)) {
                    this.kickOut(onlineUser.getKey());
                } else if (StringUtils.isBlank(igoreToken)) {
                    this.kickOut(onlineUser.getKey());
                }
            } catch (Exception e) {
                log.error("checkUser is error", e);
            }
        });
    }


    // =====

    /**
     * @description: 依据session加密信息
     * @param: SessionContext 用户session
     * @return: JwtAccessToken 加密token
     * @author: wennn
     * @date: 5/9/20 4:40 PM
     */
    public JwtAccessToken create(SessionContext sessionContext) {

        if (StringUtils.isEmpty(sessionContext.getUsername())) {
            throw new IllegalArgumentException("用户名称不能为空");
        }

        if (sessionContext.getAuthorities() == null || sessionContext.getAuthorities().isEmpty()) {
            throw new IllegalArgumentException("用户权限信息不能为空");
        }

        Claims claims = Jwts.claims().setSubject(sessionContext.getUsername());


        SWatch stopWatch = new SWatch("生成JwtToken");

        stopWatch.start("加密用户信息key");
        // 用于存用户的key
        String key = null;
        try {
            key = EncryptUtils.desEncrypt(sessionContext.getUsername() + sessionContext.getSessionUser().getId());
        } catch (Exception e) {
            throw new IllegalArgumentException("用户key加密失败");
        }
        stopWatch.stop();

        stopWatch.start("生成 Jwt token");
        // 加密
        LocalDateTime nowTime = LocalDateTime.now();
        String token = Jwts.builder()
                .claim(SecurityConst.JWT_CLAIMS_SCOPE_KEY, sessionContext.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.joining(",")))
                .claim(SecurityConst.JWT_CLAIMS_USER_KEY, key)
                .setIssuer(jwtSetting.getTokenIssuer())
                .setIssuedAt(Date.from(nowTime.atZone(ZoneId.systemDefault()).toInstant()))
                .setExpiration(Date.from(nowTime.plusMinutes(jwtSetting.getTokenExpirationTime()).atZone(ZoneId.systemDefault()).toInstant()))
                .signWith(SignatureAlgorithm.HS512, jwtSetting.getTokenSigningKey()).compact();

        sessionContext.getSessionUser().setToken(token);
        sessionContext.getSessionUser().setKey(key);
        stopWatch.stop();

        if(log.isDebugEnabled()){
            log.debug(stopWatch.prettyResult());
        }

        return new JwtAccessToken(token, claims);
    }

    /**
     * 解析token
     *
     * @param accessToken
     * @return
     */
    public SessionContext parseSession(JwtRawAccessToken accessToken) {
        SWatch stopWatch = new SWatch("解析JWT Token");

        stopWatch.start("解析Token");
        Jws<Claims> claimsJws = accessToken.parseClaims(this.jwtSetting.getTokenSigningKey());
        stopWatch.stop();

        Claims claims = Optional.ofNullable(claimsJws).orElseThrow(() -> new NonceExpiredException("登陆信息过期")).getBody();

        SessionContext sessionContext = new SessionContext();

        Optional.ofNullable(claims).ifPresent(s -> {

            stopWatch.start("解析权限");
            Collection<? extends GrantedAuthority> authorities =
                    Arrays.stream(claims.get(SecurityConst.JWT_CLAIMS_SCOPE_KEY).toString().split(","))
                            .map(SimpleGrantedAuthority::new)
                            .collect(Collectors.toList());
            stopWatch.stop();

            stopWatch.start("解析用户");
            Optional.ofNullable(s.get(SecurityConst.JWT_CLAIMS_USER_KEY)).ifPresent(i -> {
                SessionUser sessionUser = getCacheUser(i.toString());
                if (null == sessionUser) {
                    throw new TokenException("用户信息过期");
                }
                sessionContext.setSessionUser(sessionUser);
            });
            stopWatch.stop();
            sessionContext.setAuthorities(authorities);
        });

        if (log.isDebugEnabled()) {
            log.debug(stopWatch.prettyResult());
        }
        return sessionContext;
    }


    /**
     * 解析 token 中 Redis中用户session信息
     * @param accessToken
     * @return
     */
    public Optional<SessionContext> parseSessionUser(JwtRawAccessToken accessToken){
        String tokenStr = accessToken.getToken();
        Map<String,String> map = JwtUtil.parseJwtPayload(tokenStr);
        String uKey = map.get(SecurityConst.JWT_CLAIMS_USER_KEY);
        SessionUser cacheUser = getCacheUser(uKey);
        if(null == cacheUser) {
            return Optional.ofNullable(null);
        }

        String scope = map.get(SecurityConst.JWT_CLAIMS_SCOPE_KEY);
        SessionContext sContext = SessionContext.builder(cacheUser);
        // 解析权限
        Optional.ofNullable(scope).ifPresent(s-> sContext.setAuthorities(Arrays.stream(s.split(",")).map(SimpleGrantedAuthority::new).collect(Collectors.toList())));

        return Optional.ofNullable(sContext);
    }


    // 清除 session 缓存
    public void removeSession(String token){
        if(null == token){
            return;
        }
        SessionContext sessionContext = null;
        try {
            sessionContext = parseSession(new JwtRawAccessToken(JwtUtil.praseHeaderToken(token)));
            // 清除 redis 缓存就行了
            Optional.ofNullable(sessionContext.getSessionUser()).ifPresent(sessionUser -> {
                clearCacheUser(sessionUser.getKey());
            });
        }catch (Exception e){

        }
    }

    private SessionUser getCacheUser(String key) {
        SessionUser sessionUser = (SessionUser) redisUtils.get(jwtSetting.getOnlineKey() + key);
        if (log.isDebugEnabled()) {
            log.debug("获取用户 Key ：{} 获取：{}", key, sessionUser);
        }
        return sessionUser;
    }

    public void cacheSessionUser(String key, SessionUser sessionUser) {
        if (log.isDebugEnabled()) {
            log.debug("缓存用户 Key {} == {}", key, sessionUser);
        }
        redisUtils.set(jwtSetting.getOnlineKey() + key, sessionUser, 60 * jwtSetting.getTokenRedisExpirationTime());
    }

    private void clearCacheUser(String key){
        redisUtils.del(jwtSetting.getOnlineKey() + key);
    }

    /**
     * List 分页
     */
    public static List toPage(int page, int size, List list) {
        int fromIndex = page * size;
        int toIndex = page * size + size;
        if (fromIndex > list.size()) {
            return new ArrayList();
        } else if (toIndex >= list.size()) {
            return list.subList(fromIndex, list.size());
        } else {
            return list.subList(fromIndex, toIndex);
        }
    }
}
